package com.aya.base.job.module.job;

import com.aya.base.job.ext.BeanInvokingJobDetailFactoryBean;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class ScheduleBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor {

    private String triggerName;
    private BeanDefinitionRegistry registry;
    Set<String> scheduleNames = new HashSet<>();

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

        String[] beanDefinitionNames = registry.getBeanDefinitionNames();

        List<String> triggerNames = new ArrayList<>();
        for (String beanDefinitionName : beanDefinitionNames) {
            Class<?> aClass = beanFactory.getType(beanDefinitionName);
            ReflectionUtils.doWithMethods(aClass, method -> {
                ScheduleCallback annotation = AnnotationUtils.findAnnotation(method, ScheduleCallback.class);
                if (annotation != null) {
                    String scheduleName = annotation.name();
                    addScheduleName(scheduleName);
                    registerWork(registry, scheduleName, method, beanDefinitionName);
                    String generateTriggerName = registerTrigger(registry, scheduleName);
                    triggerNames.add(generateTriggerName);
                }
            });

        }
        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition();
        rootBeanDefinition.setBeanClass(ArrayList.class);
        ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues();
        for (String triggerName : triggerNames) {
            constructorArgumentValues.addIndexedArgumentValue(0, new RuntimeBeanReference(triggerName));
        }

        rootBeanDefinition.setConstructorArgumentValues(constructorArgumentValues);
        registry.registerBeanDefinition(triggerName, rootBeanDefinition);
    }

    private void addScheduleName(String scheduleName) {
        if (!scheduleNames.add(scheduleName)) {
            throw new BeanCreationException("存在相同的调度名称:" + scheduleName);
        }
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        this.registry = registry;


    }

    private void register(BeanDefinitionRegistry registry) {

    }

    private String registerTrigger(BeanDefinitionRegistry registry, String scheduleName) {
        RootBeanDefinition workBeanDefinition = new RootBeanDefinition();

        Class<?> cronTriggerFactoryBeanClass = CronTriggerFactoryBean.class;
        workBeanDefinition.setBeanClass(cronTriggerFactoryBeanClass);
        MutablePropertyValues propertyValues = new MutablePropertyValues();
        String jobDetailBeanName = scheduleName + "Work";
        propertyValues.add("jobDetail", new RuntimeBeanReference(jobDetailBeanName));
        // 用 scheduleName 映射 cron 表达式
        propertyValues.add("cronExpression", "0/5 * * * * ?");
        propertyValues.add("misfireInstructionName", "MISFIRE_INSTRUCTION_DO_NOTHING");
        workBeanDefinition.setPropertyValues(propertyValues);
        String beanName = scheduleName + "Trigger";
        registry.registerBeanDefinition(beanName, workBeanDefinition);
        return beanName;
    }

    private void registerWork(BeanDefinitionRegistry registry, String scheduleName, Method method, String beanName) {
        RootBeanDefinition workBeanDefinition = new RootBeanDefinition();

        Class<?> beanInvokingJobDetailFactoryBeanClass = BeanInvokingJobDetailFactoryBean.class;
//        Class<?> beanInvokingJobDetailFactoryBeanClass = JobDetailFactoryBean.class;
        workBeanDefinition.setBeanClass(beanInvokingJobDetailFactoryBeanClass);
        MutablePropertyValues propertyValues = new MutablePropertyValues();
        propertyValues.add("targetBean", beanName);
        propertyValues.add("targetMethod", method.getName());
        propertyValues.add("durability", true);
        workBeanDefinition.setPropertyValues(propertyValues);


        registry.registerBeanDefinition(scheduleName + "Work", workBeanDefinition);
    }

    public String getTriggerName() {
        return this.triggerName;
    }

    public void setTriggerName(String triggerName) {
        this.triggerName = triggerName;
    }
}
